package name.fordj.glutil.index;

import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Index<T> {
    private final String id;
    private final Map<Object, EventList<T>> valueToEventList = new HashMap<Object, EventList<T>>();
    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public Index(String id) {
        this.id = id;
    }

    public T getUnique(Object value) {
        try {
            readWriteLock.readLock().lock();
            EventList<T> list = valueToEventList.get(value);
            if(list == null) {
                return null;
            }
            try {
                list.getReadWriteLock().readLock().lock();
                if(list.size() == 1) {
                    return list.get(0);
                }
            } finally {
                list.getReadWriteLock().readLock().unlock();
            }
            throw new IllegalStateException("More than one object maps to id " + id);
        } finally {
            readWriteLock.readLock().unlock();
        }
    }

    /**
     * Any access to this list must be managed by this lists ReadWriteLock
     * @param value
     * @return
     */
    public EventList<T> get(Object value) {
        try {
            readWriteLock.readLock().lock();
            return valueToEventList.get(value);
        } finally {
            readWriteLock.readLock().unlock();
        }
    }

    /**
     * Any access to this list must be managed by this lists ReadWriteLock
     * @param value
     * @return
     */
    public EventList<T> getAndCreateIfNotExist(Object value) {
        try {
            readWriteLock.readLock().lock();

            EventList<T> result = valueToEventList.get(value);
            if(result == null) {
                result = new BasicEventList<T>();
                try {
                    readWriteLock.readLock().unlock();
                    readWriteLock.writeLock().lock();

                    valueToEventList.put(value, result);
                } finally {
                    readWriteLock.readLock().lock();
                    readWriteLock.writeLock().unlock();
                }
            }
            return result;
        } finally {
            readWriteLock.readLock().unlock();
        }
    }

    public void remove(T removed) {
        try {
            readWriteLock.readLock().lock();
            for (Object value : valueToEventList.keySet()) {
                EventList<T> list = valueToEventList.get(value);
                try {
                    list.getReadWriteLock().writeLock().lock();
                    if(list.remove(removed)) {
                        if(list.isEmpty()) {
                            //TODO: should I auto remove empty lists?
                        }
                    }
                } finally {
                    list.getReadWriteLock().writeLock().unlock();
                }
            }
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}
